package com.sojson.util.mq.rabbit.code.interfaces.impl;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import com.rabbitmq.client.AMQP.BasicProperties.Builder;
import com.rabbitmq.client.AMQP.Queue.BindOk;
import com.rabbitmq.client.Channel;
import com.sojson.util.StringUtil;
import com.sojson.util.mq.rabbit.code.interfaces.IRabbitCodeConsumeUtil;
import com.sojson.util.mq.rabbit.code.interfaces.IRabbitCodeExchangeUtil;
import com.sojson.util.mq.rabbit.code.interfaces.IRabbitCodeMsgUtil;
import com.sojson.util.mq.rabbit.code.interfaces.IRabbitCodeQueueUtil;
import com.sojson.util.mq.rabbit.enums.ERabbitExchange;

/**
 * Rabbit-Direct(指定匹配)工具类</BR>
 * 按照routingkey分发到指定队列,处理路由键，需要将一个队列绑定到交换机上</BR>
 * 如果绑定交换机和发送消息的时候都没有指定routingkey,就和{@link com.sojson.util.mq.rabbit.code.interfaces.impl.RabbitCodeFanoutUtilImpl}功能一样
 * 
 * @author liu
 * @date 2022-06-16
 */
public class RabbitCodeDirectUtilImpl implements IRabbitCodeExchangeUtil, IRabbitCodeQueueUtil, IRabbitCodeMsgUtil, IRabbitCodeConsumeUtil {

    private static RabbitCodeDirectUtilImpl instances;

    /**
     * 将交换机和队列绑定
     * 
     * @param channel
     * @param exchange      交换机名称
     * @param queue         队列名称
     * @return
     * @throws IOException
     */
    public BindOk queueBind(Channel channel, String exchange, String queue) throws IOException {
        return queueBind(channel, exchange, queue, getQueueBindParam());
    }

    /**
     * 将交换机和队列绑定
     * 
     * @param channel
     * @param exchange      交换机名称
     * @param queue         队列名称
     * @param arguments     其他参数(默认为null)
     * @return
     * @throws IOException
     */
    public BindOk queueBind(Channel channel, String exchange, String queue, Map<String, Object> arguments)
        throws IOException {
        return queueBind(channel, exchange, queue, StringUtil.EMPTY, getQueueBindParam());
    }

    /**
     * 将交换机和队列绑定
     * 
     * @param channel
     * @param exchange      交换机名称
     * @param queue         队列名称
     * @param routingKey    路由键
     * @return
     * @throws IOException
     */
    public BindOk queueBind(Channel channel, String exchange, String queue, String routingKey) throws IOException {
        return queueBind(channel, exchange, queue, routingKey, getQueueBindParam());
    }

    /**
     * 将交换机和队列绑定
     * 
     * @param channel
     * @param exchange      交换机名称
     * @param queue         队列名称
     * @param routingKey    路由键
     * @param arguments     其他参数(默认为null)
     * @return
     * @throws IOException
     */
    public BindOk queueBind(Channel channel, String exchange, String queue, String routingKey,
        Map<String, Object> arguments) throws IOException {
        return channel.queueBind(getQueueName(queue), getExchangeName(exchange), getRoutingKeyName(routingKey),
            arguments);
    }

    /**
     * 开始生产消息
     * 
     * @param channel
     * @param exchange      交换机的名称
     * @param routingKey    消息的Key(如果没有交换机,此时是将消息发布到的队列名)
     * @param body          内容
     * @return
     * @throws IOException
     */
    public void basicPublish(Channel channel, String exchange, String routingKey, String body) throws IOException {
        basicPublish(channel, exchange, routingKey, false, body);
    }

    /**
     * 开始生产消息
     * 
     * @param channel
     * @param exchange      交换机的名称
     * @param routingKey    消息的Key(如果没有交换机,此时是将消息发布到的队列名)
     * @param mandatory     是否开启消息故障检测模式
     *                      它只会让RabbitMq向你通知失败,而不会通知成功
     *                      设为true的时候,使用前要先调用channel.addReturnListener(new ReturnCallback(){})
     *                      channel.addReturnListener(new ReturnListener(){})为异步返回
     *                      如果为true,消息不能路由到指定的队列时,会触发channel.BasicReturn事件
     *                      如果为false,则broker会直接将消息丢弃
     * @param immediate     RabbitMQ 3.0版本开始去掉了对immediate参数的支持
     *                      原因是因为immediate参数会影响镜像队列的性能,增加了代码的复杂度,建议采用TTL和DLX的方法替代
     *                      当immediate参数设置为true时,如果交换器再将消息路由到一个队列中,否则将消息返回给生产者
     *                      immediate参数告诉服务器,如果该消息关联的队列上有消费者,则立刻投递
     *                      如果所有匹配的队列上都没有消费者,则直接将消息返还给生产者,不用将消息存于的队列而等待消费者
     * @param body          内容
     * @return
     * @throws IOException
     */
    public void basicPublish(Channel channel, String exchange, String routingKey, boolean mandatory, String body)
        throws IOException {
        basicPublish(channel, exchange, routingKey, mandatory, false, getBasicProperties(), body);
    }

    /**
     * 开始生产消息
     * 
     * @param channel
     * @param exchange      交换机的名称
     * @param routingKey    消息的Key(如果没有交换机,此时是将消息发布到的队列名)
     * @param mandatory     是否开启消息故障检测模式
     *                      它只会让RabbitMq向你通知失败,而不会通知成功
     *                      设为true的时候,使用前要先调用channel.addReturnListener(new ReturnCallback(){})
     *                      channel.addReturnListener(new ReturnListener(){})为异步返回
     *                      如果为true,消息不能路由到指定的队列时,会触发channel.BasicReturn事件
     *                      如果为false,则broker会直接将消息丢弃
     * @param immediate     RabbitMQ 3.0版本开始去掉了对immediate参数的支持
     *                      原因是因为immediate参数会影响镜像队列的性能,增加了代码的复杂度,建议采用TTL和DLX的方法替代
     *                      当immediate参数设置为true时,如果交换器再将消息路由到一个队列中,否则将消息返回给生产者
     *                      immediate参数告诉服务器,如果该消息关联的队列上有消费者,则立刻投递
     *                      如果所有匹配的队列上都没有消费者,则直接将消息返还给生产者,不用将消息存于的队列而等待消费者
     * @param props         其它的一些属性(内容格式,类型,请求头等)
     * @param body          内容
     * @return
     * @throws IOException
     */
    public void basicPublish(Channel channel, String exchange, String routingKey, boolean mandatory, boolean immediate,
        Builder props, String body) throws IOException {
        channel.basicPublish(getExchangeName(exchange), getRoutingKeyName(routingKey), mandatory,
            immediate, props.build(), body.getBytes());
    }

    /** 获取 交换机和队列绑定时的属性 */
    public Map<String, Object> getQueueBindParam() {
        Map<String, Object> map = new HashMap<>(10);
        return map;
    }

    /**
     * 获取交换机类型
     * 
     * @return
     */
    @Override
    public String getExchangeType() {
        return ERabbitExchange.DIRECT.getCode();
    }

    /**
     * 判断一个对象是否不是空(空对象)
     * 
     * @param obj
     * @return
     */
    private static boolean isBlankObject(Object obj) {
        return StringUtil.isBlankObject(obj);
    }

    public static RabbitCodeDirectUtilImpl getInstances() {
        if (isBlankObject(instances)) {
            instances = new RabbitCodeDirectUtilImpl();
        }
        return instances;
    }

}